# -*- coding: utf-8 -*-
"""
Wargame AI的“上层AI”
YuXH, 2020-03
"""
from myAI.hexmap.hex import HexOffset
from myAI.const import StrategicEventType, LocalEventType, TargetTaskType
import copy
from core.utils.map import Map
import random
from myAI.infer_paras import *
from myAI.localEvents import *


class FactionAI:
    def __init__(self, color, scenario, scenario_info):
        self.color = color
        self.lastobservation = None #保存上一步状态，便于和当前状态比较
        self.task = {}  #保存算子分配的任务信息{obj_id:['outputfire',param1,param2]}
        self.tactic = None  # 我方策略，'1-attack' | '2-defend'
        self.group_info = []     #保存算子分组信息 [{group_id: 1, group_task: {ToOccupy:city}, operators: [opr1, opr2}]
        self.map = Map(scenario)  # use 'Map' class as a tool
        self.scenario_info = scenario_info  #保存初始想定信息
        self.enemies = []       #保存敌方算子信息 [{obj_id:14977, operator:opr, step:12}]

    def factionAI_reset(self):
        self.color = None
        self.lastobservation = None
        self.task = None
        self.tactic = None
        self.group_info = None
        self.map = None
        self.scenario_info = None
        self.enemies = None

    def update(self, observation):
        # 根据observation找出敌我双方算子，包括车上算子
        our_oprs, enemies = self.partition_operators(observation)

        # 根据observation更新观察到的敌方算子信息
        if enemies:
            self.updateEnemies(enemies, observation['time']['cur_step'])

        # 1.1生成战略事件
        StrategicEvent = self.genStrategicEvent(observation)
        # 如果有战略事件，则处理
        if StrategicEvent is not None:
            print(f'StrategicEvent：{StrategicEvent}')
            # 首先更新一下factionAI的内部数据
            self.updateFactionAIData(observation)
            # 1.2处理战略事件
            # ipcArgs.mutex_task.acquire()
            self.handleStrategicEvent(StrategicEvent, our_oprs, observation)
            # ipcArgs.mutex_task.release()
        # 没有战略事件，则生成局部事件
        else:
            # 1.3生成局部事件，每次生成一个局部事件
            LocalEvent = self.genLocalEvent(observation, our_oprs, enemies)
            while LocalEvent:
                print(f'LocalEvent：{LocalEvent}')
                # 1.4处理局部事件，每次处理一个局部事件
                # ipcArgs.mutex_task.acquire()
                self.handleLocalEvent(observation, our_oprs, LocalEvent)
                # ipcArgs.mutex_task.release()
                LocalEvent = self.genLocalEvent(observation, our_oprs, enemies)

        # 保存本step的状态
        # 可以认为每次更新过来的observation都是一个新的对象，这里不存在引用的关系
        self.lastobservation = observation

    # 主要更新敌方算子列表信息（将敌方打死，发生比分的变化）
    # 其他分组任务信息由于发生了战略事件，会重新分配生成,不用在这里更新
    def updateFactionAIData(self, observation):
        for judge_info in observation['judge_info']:
            last_enemy_opr = self.get_enemybop_lastobservation(self.lastobservation, judge_info['target_obj_id'])
            if last_enemy_opr:
                if judge_info['damage'] >= last_enemy_opr['blood']:
                    print('obj_id:%s  is DEAD !' % (judge_info['target_obj_id']))
                    for enemy_info in self.enemies:
                        if enemy_info['obj_id'] == judge_info['target_obj_id']:
                            self.enemies.remove(enemy_info)
                            break

    #[{obj_id: 14977, operator: opr, step: 12}]
    # 将最近观察到的放在列表最后
    def updateEnemies(self, enemies, cur_step):
        # enemiesID_index = self.getEnemiesIDandIndex()
        for enemy in enemies:
            # 如果观察到的这个算子id在原来的敌方算子信息列表中，先将其删除
            # if enemy['obj_id'] in list(enemiesID_index.keys()):
            #     self.enemies.pop(enemiesID_index[enemy['obj_id']])
            enemyInfo = self.getEnemyInfoByID(enemy['obj_id'])
            if enemyInfo:
                self.enemies.remove(enemyInfo)
            # 再将最新观察到的敌方算子添加到列表最前面
            tmpEnemy = {}
            tmpEnemy['obj_id'] = enemy['obj_id']
            tmpEnemy['operator'] = enemy
            tmpEnemy['step'] = cur_step
            #self.enemies.append(tmpEnemy)
            self.enemies.insert(0,tmpEnemy)

        print('******* cur_step:%s *******' %(cur_step))
        for enemyInfo in self.enemies:
            print('obj_id:%s  cur_hex:%s  step:%s' %(enemyInfo['obj_id'], enemyInfo['operator']['cur_hex'], enemyInfo['step']))



    # 得到保存的敌方算子id及其在列表中的index
    def getEnemiesIDandIndex(self):
        enemiesID_index = {}
        for i, val in self.enemies:
            enemiesID_index[val['obj_id']] = i
        return enemiesID_index

    def getEnemyInfoByID(self, obj_id):
        for enemyInfo in self.enemies:
            if enemyInfo['obj_id'] == obj_id:
                return enemyInfo
        return None

    #从初始想定中得到敌方算子id列表
    def getEnemiesIDs(self):
        enemiesIDs = []
        if self.color == 0:
            for opr in self.scenario_info['1']['operators']:
                enemiesIDs.append(opr['obj_id'])
            for pas in self.scenario_info['1']['passengers']:
                enemiesIDs.append(pas['obj_id'])
        else:
            for opr in self.scenario_info['0']['operators']:
                enemiesIDs.append(opr['obj_id'])
            for pas in self.scenario_info['0']['passengers']:
                enemiesIDs.append(pas['obj_id'])
        return enemiesIDs




    # 从observation获取指定敌方obj_id的算子
    def get_enemybop_observation(self, observation, obj_id):
        """
        Get bop in factionAI observation based on its id.
        """
        for bop in observation['operators']:
            if bop['color'] != self.color and obj_id == bop['obj_id']:
                return bop
        for bop in observation['passengers']:
            if bop['color'] != self.color and obj_id == bop['obj_id']:
                return bop
        return None

    # 从observation获取指定我方obj_id的算子
    def get_ourbop_observation(self, observation, obj_id):
        """
        Get bop in factionAI observation based on its id.
        """
        for bop in observation['operators']:
            if bop['color'] == self.color and obj_id == bop['obj_id']:
                return bop
        for bop in observation['passengers']:
            if bop['color'] == self.color and obj_id == bop['obj_id']:
                return bop
        return None



    # 从observation获取指定我方type的算子
    # def get_typebop_observation(self, observation, sub_type):
    #     """
    #     Get typebop in factionAI observation based on its type.
    #     """
    #     for bop in observation['operators']:
    #         if bop['color'] == self.color and sub_type == bop['sub_type']:
    #             return bop
    #     for bop in observation['passengers']:
    #         if bop['color'] == self.color and sub_type == bop['sub_type']:
    #             return bop
    #     return None

    # 从战车上获取指定type的算子
    def get_bop_ifv_type(self, observation, ifv_opr, sub_type):
        """
        Get typebop in factionAI ifv based on its type.
        """
        for passengerid in ifv_opr['passenger_ids']:
            passenger_opr = self.get_ourbop_observation(observation, passengerid)
            if sub_type == passenger_opr['sub_type']:
                return passenger_opr
        return None
    # 从战车上获取指定obj_id的算子
    def get_bop_ifv_id(self, observation, ifv_opr, obj_id):
        """
        Get typebop in factionAI ifv based on its obj_id.
        """
        for passengerid in ifv_opr['passenger_ids']:
            passenger_opr = self.get_ourbop_observation(observation, passengerid)
            if obj_id == passenger_opr['obj_id']:
                return passenger_opr
        return None
    # 从战车上获取所属全部算子id，包括已经下车的和还在车上的
    def get_allid_ifv(self, ifv_opr):
        """
        Get bop in factionAI ifv.
        """
        all_id = ifv_opr['launch_ids'] + ifv_opr['passenger_ids']
        return all_id

    # 从lastobservation获取指定敌方obj_id的算子
    def get_enemybop_lastobservation(self, lastobservation, obj_id):
        """
        Get bop in factionAI lastobservation based on its id.
        """
        for bop in lastobservation['operators']:
            if bop['color'] != self.color and obj_id == bop['obj_id']:
                return bop
        for bop in lastobservation['passengers']:
            if bop['color'] != self.color and obj_id == bop['obj_id']:
                return bop
        return None

    # 从lastobservation获取指定我方obj_id的算子
    def get_ourbop_lastobservation(self, lastobservation, obj_id):
        """
        Get bop in factionAI lastobservation based on its id.
        """
        for bop in lastobservation['operators']:
            if bop['color'] == self.color and obj_id == bop['obj_id']:
                return bop
        for bop in lastobservation['passengers']:
            if bop['color'] == self.color and obj_id == bop['obj_id']:
                return bop
        return None

    def genStrategicEvent(self, observation):
        StrategicEvent = None
        #如果当前没有策略，表示比赛开始，产生一个开始事件
        if self.tactic is None:
            StrategicEvent = StrategicEventType.Begin
            return StrategicEvent

        # if self.lastobservation['scores']['red_win'] != observation['scores']['red_win'] or self.lastobservation['scores']['blue_win'] != observation['scores']['blue_win']:
        # 任意一方净胜分发生变化都行
        # if self.lastobservation['scores']['red_win'] != observation['scores']['red_win']:
        #     # 如果是蓝方
        #     if self.color:
        #         if observation['scores']['blue_total'] < observation['scores']['red_total'] + self.get_cities_score(observation['cities']):
        #             curr_tactic = StrategicEventType.Attack
        #         else:
        #             curr_tactic = StrategicEventType.Defend
        #     # 如果是红方
        #     else:
        #         if observation['scores']['red_total'] < observation['scores']['blue_total'] + self.get_cities_score(observation['cities']):
        #             curr_tactic = StrategicEventType.Attack
        #         else:
        #             curr_tactic = StrategicEventType.Defend
        #     if self.tactic != curr_tactic:
        #         StrategicEvent = curr_tactic
        #         return StrategicEvent

        #对方算子全部死亡事件
        # if (self.color == 0 and observation['scores']['blue_remain'] == 0) or (self.color == 1 and observation['scores']['red_remain'] == 0):
        #     curr_tactic = StrategicEventType.EnemyAllDead
        #     if self.tactic != curr_tactic:
        #         StrategicEvent = curr_tactic
        #         return StrategicEvent

        return StrategicEvent

    # 处理战略事件
    def handleStrategicEvent(self, StrategicEvent, our_oprs, observation):
        # 1.2.1确定目标任务及参数，即确定了分组数量，这里体现了作战策略
        target_task = self.genTargetTask(StrategicEvent, observation)
        if target_task:
            # 1.2.2根据分组数量，选择算子进行分组
            print(target_task)
            self.genGroup(target_task, our_oprs, observation)
            print(self.group_info)
            # 每次重新分配所有算子任务时，应将之前的信息清空
            self.task = {}
            for gi in self.group_info:
                # 1.2.3在一个分组中，进行行动任务的分派
                # [{group_id: 1, group_task: {ToOccupy: city}, operators: [opr1, opr2]}]
                self.allocate_group_tasks(gi, observation)

    def genTargetTask(self, StrategicEvent, observation):
        # #如果是蓝方
        # if self.color:
        #     return self.genBlueTargetTask(self, StrategicEvent, observation)
        # #如果是红方
        # else:
        #     return self.genRedTargetTask(self, StrategicEvent, observation)

        target_task = []
        if StrategicEvent == StrategicEventType.Begin:
            self.tactic = StrategicEventType.Attack
            #如果是蓝方，开局策略是抢占先手，可集中可分散兵力，首先抢占一个或两个夺控点，生成一个或两个夺控目标任务
            # if self.color:
            #     numOccupyPoint = random.choice([1, 2])
            #     if numOccupyPoint == 1:
            #         city = random.choice([city for city in self.observation['cities']])
            #         temTask = {}
            #         temTask[TargetTaskType.ToOccupy] = city
            #         target_task.append(temTask)
            #         return target_task
            #     elif numOccupyPoint == 2:
            #         for city in observation['cities']:
            #             temTask = {}
            #             temTask[TargetTaskType.ToOccupy] = city
            #             target_task.append(temTask)
            #             return target_task

            # 如果是蓝方，分兵两路，进行夺控
            if self.color:
                for city in observation['cities']:
                    temTask = {}
                    temTask[TargetTaskType.ToOccupy] = city
                    target_task.append(temTask)
                return target_task
            #如果是红方，开局策略是后发制人，集中兵力，生成一个夺控目标任务
            else:
                # city = random.choice([city for city in observation['cities']])
                cities = [city for city in observation['cities']]
                city = cities[0]
                temTask = {}
                temTask[TargetTaskType.ToOccupy] = city
                target_task.append(temTask)
                return target_task

        our_cities, enemy_cities = self.get_twoside_cities(observation['cities'])
        # 进攻策略
        if StrategicEvent == StrategicEventType.Attack:
            self.tactic = StrategicEventType.Attack
            # 如果己方没有一个夺控点，己方总分低于对方总分加中立点分数，此时对方可能有0,1,2个夺控点，需要进攻
            if len(our_cities) == 0:
                temTask = {}
                temTask[TargetTaskType.ToOccupy] = self.get_other_cities(observation['cities'])
                target_task.append(temTask)
                return target_task
            # 如果己方已经有一个夺控点，己方总分低于对方总分加中立点分数，此时对方可能有0,1个夺控点，需要继续进攻
            if len(our_cities) == 1:
                # 生成一个防守目标任务
                temTask1 = {}
                temTask1[TargetTaskType.ToDefend] = our_cities[0]
                target_task.append(temTask1)
                # 生成一个进攻目标任务
                temTask2 = {}
                temTask2[TargetTaskType.ToOccupy] = self.get_other_cities(observation['cities'])
                target_task.append(temTask2)
                return target_task
            #如果己方有两个夺控点，己方总分低于对方总分，说明己方算子力量大大低于对方
            # 此时有两种选择，1.原地防守（少输） 2.进攻（火力输出，争取赢）
            if len(our_cities) == 2:
                # 生成两个防守目标任务
                for city in our_cities:
                    tempTask = {}
                    tempTask[TargetTaskType.ToDefend] = city
                    target_task.append(tempTask)
                return target_task

                # 这里需要根据敌方位置信息推理火力输出点
                # temTask[TargetTaskType.ToOccupy] = None
                # target_task.append(temTask)
                # return target_task
        # 防守策略
        if StrategicEvent == StrategicEventType.Defend:
            self.tactic = StrategicEventType.Defend
            # 如果己方没有夺控点，己方总分高于对方总分加中立点分数（小概率发生），此时对方可能有0,1,2个夺控点，说明己方在兵力上占有优势，
            # 此时有两种选择，1.原地进行火力输出 2.进攻夺控（暂不考虑）
            if len(our_cities) == 0:
                # temTask = {}
                # #这里需要根据敌方位置信息推理出防守点
                # temTask[TargetTaskType.ToDefend] = None
                # target_task.append(temTask)
                # return target_task

                # 进攻
                temTask = {}
                temTask[TargetTaskType.ToOccupy] = self.get_other_cities(observation['cities'])
                target_task.append(temTask)
                return target_task

            # 如果已经有一个夺控点，己方总分高于对方总分加中立点分数，则生成一个防守目标任务即可，此时稳赢
            if len(our_cities) == 1:
                # 生成一个防守目标任务
                temTask = {}
                temTask[TargetTaskType.ToDefend] = our_cities[0]
                target_task.append(temTask)
                return target_task
            # 如果已经有两个夺控点，己方总分高于对方总分加中立点分数，则生成两个防守目标任务
            if len(our_cities) == 2:
                for city in our_cities:
                    tempTask = {}
                    tempTask[TargetTaskType.ToDefend] = city
                    target_task.append(tempTask)
                return target_task
        # 当对方全部死亡时
        if StrategicEvent == StrategicEventType.EnemyAllDead:
            self.tactic = StrategicEventType.EnemyAllDead
            # 己方没有夺控点，需要占领所有夺控点，生成两个进攻目标任务
            if len(our_cities) == 0:
                for city in observation['cities']:
                    tempTask = {}
                    tempTask[TargetTaskType.ToOccupy] = city
                    target_task.append(tempTask)
                return target_task

            # 己方有一个夺控点，需要占领另一个夺控点
            if len(our_cities) == 1:
                # 生成一个进攻目标任务
                temTask = {}
                temTask[TargetTaskType.ToOccupy] = self.get_other_cities(observation['cities'])
                target_task.append(temTask)
                return target_task
            # 己方有两个夺控点，不需要进行任何动作了
            if len(our_cities) == 2:
                pass
        return target_task

    def genBlueTargetTask(self,StrategicEvent, observation):
        target_task = []
        # 开局策略是抢占先手，首先抢占一个或两个夺控点，生成一个或两个夺控目标任务
        if StrategicEvent == StrategicEventType.Begin:
            self.tactic = StrategicEventType.Attack
            numOccupyPoint = random.choice([1, 2])
            if numOccupyPoint == 1:
                city = random.choice([city for city in observation['cities']])
                temTask = {}
                temTask[TargetTaskType.ToOccupy] = city
                target_task.append(temTask)
                return target_task
            elif numOccupyPoint == 2:
                for city in observation['cities']:
                    temTask = {}
                    temTask[TargetTaskType.ToOccupy] = city
                    target_task.append(temTask)
                    return target_task
            else:
                pass


    def genRedTargetTask(self, StrategicEvent, observation):
        target_task = []
        # 开局策略是后发制人，生成一个夺控目标任务
        if StrategicEvent == StrategicEventType.Begin:
            self.tactic = StrategicEventType.Attack
            city = random.choice([city for city in observation['cities']])
            temTask = {}
            temTask[TargetTaskType.ToOccupy] = city
            target_task.append(temTask)


    # target_task [target_task_type:{city}]
    # groupInfo [{group_id: 1, group_task: {ToOccupy:city}, operators: [opr1, opr2]}]
    # 目前只考虑两个分组
    def genGroup(self, target_task, our_oprs, observation):
        #每次产生分组时，都应将之前的分组信息清空
        self.group_info = []
        numGroup = len(target_task)
        # 如果有一个目标任务，所有算子都分到这个组执行分组目标任务
        if numGroup == 1:
            tmpGroupInfo = {}
            tmpGroupInfo['group_id'] = 0
            tmpGroupInfo['group_task'] = target_task[0]
            tmpGroupInfo['operators'] = our_oprs
            self.group_info.append(tmpGroupInfo)

        # 如果有两个目标任务,兵力划分为两部分
        elif numGroup == 2:
            # 判断两个任务是否都为防守还是一个进攻一个防守
            # 如果相同，说明为两个防守任务（开局是蓝方两个进攻任务或者对方全部死亡时的两个进攻任务）
            if target_task[0].keys() == target_task[1].keys():
                key, = target_task[0]
                devided_list = self.divideIntoMean(our_oprs, observation['cities'], key)
                for i in range(numGroup):
                    tmpGroupInfo = {}
                    tmpGroupInfo['group_id'] = i
                    tmpGroupInfo['group_task'] = target_task[i]
                    tmpGroupInfo['operators'] = devided_list[i]
                    self.group_info.append(tmpGroupInfo)
            # 如果不同，说明为一个进攻一个防守任务
            else:
            # 平均分兵策略
                # devided_list = self.divideIntoNstrand(our_oprs, numGroup)
                # 留一分兵策略
                devided_list = self.divideOneOperators(our_oprs, target_task[0][TargetTaskType.ToDefend])
                for i in range(numGroup):
                    tmpGroupInfo = {}
                    tmpGroupInfo['group_id'] = i
                    tmpGroupInfo['group_task'] = target_task[i]
                    tmpGroupInfo['operators'] = devided_list[i]
                    self.group_info.append(tmpGroupInfo)

    # groupInfo [{group_id: 1, group_task: {ToOccupy:city}, operators: [opr1, opr2]}]
    def getGroupInfoByOpr(self, opr):
        for group_info in self.group_info:
            for group_opr in group_info['operators']:
                if group_opr['obj_id'] == opr['obj_id']:
                    return group_info
        return None

    # 根据分组任务，对本组内所有算子进行行动任务分配
    # {group_id: 1, group_task: {ToOccupy: city}, operators: [opr1, opr2]}
    def allocate_group_tasks(self, group_infor, observation):
        key, = group_infor['group_task']
        value = group_infor['group_task'][key]
        # 如果分组任务是进攻
        if key == TargetTaskType.ToOccupy:
            tar_area = self.infer_control_area(value, 3)
            # 如果是蓝方
            if self.color:
                for gro_opr in group_infor['operators']:
                    # 如果是坦克，分配火力输出任务
                    if gro_opr['sub_type'] == 0:
                        self.task[gro_opr['obj_id']] = ['output_firepower', tar_area]
                    # 如果是战车，分配火力输出任务
                    elif gro_opr['sub_type'] == 1:
                        self.task[gro_opr['obj_id']] = ['output_firepower', tar_area]
                    # 如果是人员，分配夺控任务
                    elif gro_opr['sub_type'] == 2:
                        if value:
                            self.task[gro_opr['obj_id']] = ['control', value['coord']]
                        else:
                            self.task[gro_opr['obj_id']] = ['output_firepower', []]

            # 如果是红方
            else:
                for gro_opr in group_infor['operators']:
                    # 如果是坦克，分配火力输出任务
                    if gro_opr['sub_type'] == 0:
                        # self.task[gro_opr['obj_id']] = ['output_firepower', tar_area]
                        # self.task[gro_opr['obj_id']] = ['scout', tar_area]
                        self.task[0] = ['scout', tar_area]
                        # self.task[gro_opr['obj_id']] = ['control', value['coord']]
                    # 如果是战车，分配下车任务
                    # elif gro_opr['sub_type'] == 1:
                    #     # man_bop = self.get_bop_ifv_type(observation, gro_opr, 2)
                    #     cm_bop = self.get_bop_ifv_type(observation, gro_opr, 7)
                    #     # self.task[gro_opr['obj_id']] = ['unload', [man_bop['obj_id'], cm_bop['obj_id']]]
                    #     self.task[gro_opr['obj_id']] = ['unload', cm_bop['obj_id']]
                    # # 如果是人员，分配夺控任务
                    # elif gro_opr['sub_type'] == 2:
                    #     self.task[gro_opr['obj_id']] = ['control', value['coord']]
                    # # 如果是无人车，分配火力输出任务
                    # elif gro_opr['sub_type'] == 4:
                    #     # self.task[gro_opr['obj_id']] = ['scout', tar_area]
                    #     self.task[gro_opr['obj_id']] = ['output_firepower', tar_area]
                    # # 如果是巡飞弹，分配专门任务
                    # elif gro_opr['sub_type'] == 7:
                    #     self.task[gro_opr['obj_id']] = ['cruise_missile', self.infer_missle_points(observation)]  #巡飞弹的目标区域

                    # if gro_opr['sub_type'] == 1:
                    #     # self.task[gro_opr['obj_id']] = ['unload', [14987, 14984]] # 测试
                    #     man_bop = self.get_bop_ifv_type(observation, gro_opr, 2)
                    #     cm_bop = self.get_bop_ifv_type(observation, gro_opr, 7)
                    #     self.task[gro_opr['obj_id']] = ['unload', [man_bop['obj_id'], cm_bop['obj_id']]]
                    #     # self.allocate_ifv_task(gro_opr, observation)
                    # elif gro_opr['sub_type'] == 2:
                    #     self.task[gro_opr['obj_id']] = ['control', value['coord']]
                    # elif gro_opr['sub_type'] == 7:
                    #     self.task[gro_opr['obj_id']] = ['cruise_missile', self.infer_missle_points(observation)]  #巡飞弹的目标区域

                    # if gro_opr['sub_type'] == 1:
                    #     self.task[gro_opr['obj_id']] = ['support_firepower', tar_area]
                    # elif gro_opr['sub_type'] == 2:
                    #     if value:
                    #         self.task[gro_opr['obj_id']] = ['control', value['coord']]
                    #     else:
                    #         self.task[gro_opr['obj_id']] = ['output_firepower', tar_area]

                    # tar_area = self.infer_fire_area(3018, 1)
                    # if gro_opr['sub_type'] == 1:
                    #     self.task[gro_opr['obj_id']] = ['support_firepower', tar_area]
                    # elif gro_opr['sub_type'] == 4:
                    #     self.task[gro_opr['obj_id']] = ['scout', tar_area]

        # 如果分组任务是防守
        else:
            for gro_opr in group_infor['operators']:
                # 如果是人进行反夺控
                if gro_opr['sub_type'] == 2:
                    self.task[gro_opr['obj_id']] = ['defend_firepower', value['coord'], []]
                # 其他类型算子面向敌方向防守夺控点
                else:
                    defend_direction = self.recent_target_direction(gro_opr['cur_hex'])
                    self.task[gro_opr['obj_id']] = ['defend_firepower', value['coord'], defend_direction]

###########################################################################################################
    # 生成局部事件,一次只产生一个局部事件
    def genLocalEvent(self, observation, our_oprs, enemy_oprs):
        localevent = {}
        # 我方算子死亡事件
        localevent = genLocalEventOurDead(our_oprs, self)
        if localevent:
            return localevent

        # 算子下车事件
        localevent = genLocalEventGetOff(our_oprs, self)
        if localevent:
            return localevent

        # 算子被打击事件
        localevent = genLocalEventShooted(observation, self)
        if localevent:
            return localevent

        # 敌方进攻事件
        # 如果敌方出现在了夺控点附近（3格内），说明敌方要夺控此夺控点
        # for enemyInfo in self.enemies:


        # 算子夺控事件
        localevent = genLocalEventOccupy(observation, self)
        if localevent:
            return localevent


        # 算子任务状态事件,目前将其放在最后，因为执行返回信息会慢1个step
        # 根据task判断当前任务的状态，产生不同的任务状态事件
        localevent = genLocalEventTaskState(self)
        if localevent:
            return localevent

        return localevent


    # 处理局部事件
    def handleLocalEvent(self, observation, our_oprs, LocalEvent):
        # 2.2.1局部调整个别算子的行动任务
        key, = LocalEvent
        value = LocalEvent[key]

        # 如果我方算子已死亡，则删除该算子对应的任务，并更新group_info中算子信息
        if key == LocalEventType.OurDead:
            handleLocalEventOurDead(value, observation, self)
            return

        if key == LocalEventType.OurGetOff:
            handleLocalEventGetOff(value, observation, self)
            return

        # if key == LocalEventType.OurShooted:
        #     handleLocalEventOurShooted(value, observation, self)
        #     return

        if key == LocalEventType.EnemyShooted:
            handleLocalEventEnemyShooted(value, observation, self)
            return

        # 如果算子的任务已经完成，则根据其所属分组的目标任务为其分配一个新的行动任务
        if key == LocalEventType.TaskCompleted:
            handleLocalEventTaskCompleted(value, observation, self)
            return

        # 如果算子的任务执行失败，则根据其所属分组的目标任务为其分配一个新的行动任务
        if key == LocalEventType.TaskFailed:
            handleLocalEventTaskFailed(value, self)
            return

        # 如果算子的任务线程出错，则需要为该算子重新分配任务
        if key == LocalEventType.ThreadError:
            handleLocalEventThreadError(value, self)
            return

        # 如果产生己方夺控完成事件，更新分组任务中目标状态（夺控点）
        if key == LocalEventType.OurOccupy:
            handleLocalEventOurOccupy(value, observation, self)
            return

        # 如果产生敌方夺控完成事件，更新分组任务中目标状态（夺控点）
        if key == LocalEventType.EnemyOccupy:
            handleLocalEventEnemyOccupy(value, observation, self)
            return

        # 如果产生正在执行任务的事件
        if key == LocalEventType.TaskDoing:
            return
        else:
            return


    # 更新分组任务group_info中的算子operators
    def updateGroupOperators(self, obj_id):
        for gi in self.group_info:
            for opr in gi['operators']:
                if obj_id == opr['obj_id']:
                    gi['operators'].remove(opr)
                    return

    # 根据observation更新所有分组任务中的夺控点状态
    # [{group_id: 1, group_task: {ToOccupy: city}, operators: [opr1, opr2]}]
    # 注意，这里更新group_info中city的flag时，会联动修改task中的city的flag，因为task中的city就是从group_info中来的，引用关系
    def updateGroupCityFlag(self,observation):
        for city in observation['cities']:
            for gi in self.group_info:
                key, = gi['group_task']
                value = gi['group_task'][key]
                if city['coord'] == value['coord']:
                    value['flag'] = city['flag']
                    break


    # 根据算子所在分组的对单个算子进行任务分配，新分配的任务只和所在分组的目标任务相关
    def allocate_task(self, obj_id):
        # [{group_id: 1, group_task: {ToOccupy: city}, operators: [opr1, opr2]}]
        for gi in self.group_info:
            key, = gi['group_task']
            value = gi['group_task'][key]
            for opr in gi['operators']:
                if opr['obj_id'] == obj_id:
                    # 如果当前分组任务是进攻，分配一个进攻任务，火力输出
                    if key == TargetTaskType.ToOccupy:
                        # 如果目标夺控点不为本方，表示目前还未夺控下来，继续进行夺控或者火力输出
                        if value['flag'] != self.color:
                            #判断是否有算子分配了夺控行动任务
                            for id, taskParam in self.task.items():
                                #如果已经有算子执行夺控行动任务，本算子分配火力输出行动任务
                                if taskParam[0] == 'control':
                                    tar_area = self.infer_control_area(value, 5)
                                    self.task[obj_id] = ['output_firepower', tar_area]
                                    break
                            #如果所有其他算子没有执行夺控行动任务的，本算子分配夺控行动任务
                            else:
                                self.task[obj_id] = ['control', value['coord']]
                        # 如果目标夺控点为本方，表示目前已夺控下来，进行防守
                        else:
                            #将本组目标任务改为防守,目标点不变，后面同组的其他算子完成之前的行动任务后都会转防守行动任务
                            gi['group_task'][TargetTaskType.ToDefend] = gi['group_task'].pop(TargetTaskType.ToOccupy)

                            if opr['sub_type'] == 2:
                                self.task[obj_id] = ['defend_firepower', value['coord'], []]
                            else:
                                self.task[obj_id] = ['defend_firepower', value['coord'], self.recent_target_direction(opr['cur_hex'])]
                    # 如果当前分组任务是防守
                    else:
                        # 原地进行防守任务
                        if opr['sub_type'] == 2:
                            self.task[obj_id] = ['defend_firepower', value['coord'], []]
                        else:
                            self.task[obj_id] = ['defend_firepower', opr['cur_hex'], [random.randint(0, 5)]]
                    return

    def allocate_tank_task(self, opr_tank):
        # enemy_direction = self.recent_target_direction(opr_tank['cur_hex'])
        # # 寻找最近看到的敌人所在方向进行火力输出
        # if enemy_direction:
        #     our_opr_hex = HexOffset(opr_tank['cur_hex'] // 100, opr_tank['cur_hex'] % 100)
        #     # 半径为d射击距离上的圆弧
        #     for d in enemy_direction:
        #         range_hex = our_opr_hex.get_arc(5, d)
        #         range_hex += range_hex
        #     scout_area = []
        #     for hex in range_hex:
        #         scout_area.append(hex.row * 100 + hex.col)
        #     self.task[opr_tank['obj_id']] = ['output_firepower', scout_area]
        # # 原地进行防守任务
        # else:
        #     self.task[opr_tank['obj_id']] = ['defend_firepower', opr_tank['cur_hex'], [random.randint(0, 5)]]

        enemy_area = self.recent_target_area()
        if enemy_area:
            self.task[opr_tank['obj_id']] = ['output_firepower', enemy_area]
        else:
            self.task[opr_tank['obj_id']] = ['defend_firepower', opr_tank['cur_hex'], [random.randint(0, 5)]]

    # groupInfo [{group_id: 1, group_task: {ToOccupy:city}, operators: [opr1, opr2]}]
    def allocate_ifv_task(self, opr_ifv, observation):
        # 如果战车发射的算子中有巡飞弹，则不需要再发射巡飞弹了，直接跳过
        for launch_id in opr_ifv['launch_ids']:
            launch_opr = self.get_ourbop_observation(observation, launch_id)
            if launch_opr['sub_type'] == 7:
                break
        # 如果战车发射的算子中没有巡飞弹，优先给战车下达巡飞弹下车任务
        else:
            # 目前巡飞弹下车的策略是等当前战车任务结束后，向战车下达巡飞弹下车任务
            # for passengerid in opr_ifv['passenger_ids']:
            #     cur_passenger_opr = self.get_ourbop_observation(observation, passengerid)
            #     # 如果还有一个巡飞弹，则给战车下达巡飞弹下车的任务
            #     if cur_passenger_opr['sub_type'] == 7:
            #         self.task[opr_ifv['obj_id']] = ['unload', passengerid]
            #         # 避免后续在再给战车下其他任务
            #         return

            cm_opr = self.get_bop_ifv_type(observation, opr_ifv, 7)
            # 如果战车上还有巡飞弹，向战车下达巡飞弹下车任务
            if cm_opr:
                self.task[opr_ifv['obj_id']] = ['unload', cm_opr['obj_id']]


        # 如果战车上的巡飞弹已发射，并且战车上人员有任务，则给战车分配人员下车任务
        for obj_id in self.task.keys():
            our_opr = self.get_ourbop_observation(observation, obj_id)
            if our_opr['sub_type'] == 2 and our_opr['car'] == opr_ifv['obj_id']:
                if our_opr['on_board'] == 1:
                    self.task[opr_ifv['obj_id']] = ['unload', our_opr['obj_id']]
                    break
        # 如果战车上人员没有任务，则给战车分配火力支援任务
        else:
            group_info = self.getGroupInfoByOpr(opr_ifv)
            if group_info:
                key, = group_info['group_task']
                value = group_info['group_task'][key]
                tar_area = self.infer_control_area(value, 5)
                self.task[opr_ifv['obj_id']] = ['support_firepower', tar_area]

    def occupyAllCity(self, observation):
        cities = observation['cities']
        for city in cities:
            if city['flag'] != self.color:
                #前提对方全部死亡，operators只有我方算子
                ctrl_opr = self.select_ctrl_opr(observation['operators'])
                if ctrl_opr:
                    self.task[ctrl_opr['obj_id']] = ['control', city['coord']]

########################################################################################################################

    # 将一个list尽量均分成n份，限制len(list)==n，份数大于原list内元素个数则分配空list[]
    def divideIntoNstrand(self, listTemp, n):
        twoList = [[] for i in range(n)]
        for i, e in enumerate(listTemp):
            twoList[i % n].append(e)
        return twoList
    # 选择一个距离夺控点最近的算子进行防守，其他算子用于进攻另一个夺控点
    def divideOneOperators(self, our_oprs, defend_tar):
        List1 = []
        List2 = []
        defend_opr = self.select_best_opr(our_oprs, defend_tar, TargetTaskType.ToDefend)
        if defend_opr:
            List1.append(defend_opr)
            for opr in our_oprs:
                if opr['obj_id'] != defend_opr['obj_id']:
                    List2.append(opr)
        return [List1, List2]

    # 分组时，考虑战车车上算子（要在一起）
    def divideIntoMean(self, our_oprs, cities, attOrdef):
        List1 = []
        List2 = []
        local_oprs = copy.deepcopy(our_oprs)
        while len(local_oprs) > 0:
            defend_opr1 = self.select_best_opr(local_oprs, cities[0], attOrdef)
            if defend_opr1:
                local_oprs.remove(defend_opr1)
                List1.append(defend_opr1)
                # 如果选出的算子是战车，则将其下属的成员一起分到同组中
                if defend_opr1['sub_type'] == 1:
                    for pass_opr in local_oprs:
                        if pass_opr['obj_id'] == defend_opr1['passenger_ids'][0]:
                            local_oprs.remove(pass_opr)
                            List1.append(pass_opr)
                            break

                if len(local_oprs) > 0:
                    defend_opr2 = self.select_best_opr(local_oprs, cities[1], attOrdef)
                    if defend_opr2:
                        local_oprs.remove(defend_opr2)
                        List2.append(defend_opr2)
                        # 如果选出的算子是战车，则将其下属的成员一起分到同组中
                        if defend_opr2['sub_type'] == 1:
                            for pass_opr in local_oprs:
                                if pass_opr['obj_id'] == defend_opr2['passenger_ids'][0]:
                                    local_oprs.remove(pass_opr)
                                    List2.append(pass_opr)
                                    break
        return [List1, List2]

    def partition_operators(self, observation):
        """区分我方和敌方算子"""
        our_oprs = []
        enemies = []
        for opr in observation['operators']:
            if opr['color'] == self.color:
                our_oprs.append(opr)
            else:
                enemies.append(opr)
        for opr in observation['passengers']:
            if opr['color'] == self.color:
                our_oprs.append(opr)
            else:
                enemies.append(opr)
        return our_oprs, enemies

    def choose_city(self, cities):
        """选择夺控点"""
        for city in cities:
            if city['flag'] != self.color:
                return city
        return None

    def get_cities_score(self,cities):
        """得到中立夺控点分数"""
        score = 0
        for city in cities:
            if city['flag'] == -1:
                score = score + city['value']
        return score

    def get_twoside_cities(self,cities):
        """得到我方和敌方夺控点"""
        our_city_list = []
        enemy_city_list = []
        for city in cities:
            if city['flag'] == self.color:
                our_city_list.append(city)
            else:
                enemy_city_list.append(city)
        return our_city_list,enemy_city_list

    def get_other_cities(self, cities):
        """得到非我方夺控点"""
        # 优先选择中立点
        for city in cities:
            if city['flag'] == -1:
                return city
        # 其次选择敌方控制的主夺控点
        for city in cities:
            if city['name'] == '主要':
                return city


    def select_ctrl_opr(self, our_oprs):
        """选出进行夺控的算子"""
        infantry_oprs = []
        ifv_oprs = []
        tank_oprs = []
        for opr in our_oprs:
            if opr['sub_type'] == 0:
                tank_oprs.append(opr)
            elif opr['sub_type'] == 1:
                ifv_oprs.append(opr)
            elif opr['sub_type'] == 2:
                infantry_oprs.append(opr)
        if infantry_oprs:
            return infantry_oprs[0]
        if tank_oprs:
            return tank_oprs[0]
        if ifv_oprs:
            return ifv_oprs[0]
        return None

    def select_best_opr(self, our_oprs, tar_city, attOrdef):
        """选出最佳进攻或防守的算子"""
        infantry_oprs = []
        ifv_oprs = []
        tank_oprs = []
        uv_oprs = []
        oprs_tar_dis = []
        local_oprs = []
        # 考虑距离因素，对所有算子按与目标点距离排序,
        for opr in our_oprs:
            oprs_tar_dis.append(self.map.get_distance(opr['cur_hex'], tar_city['coord']))
        # 由小到大排序，得到排序后元素在排序前的索引
        indices = [index for index, value in sorted(enumerate(oprs_tar_dis), key=lambda x: x[1])]
        # 生成排序后的算子列表
        for ind in indices:
            local_oprs.append(our_oprs[ind])
        # 在排序后的算子列表中，考虑算子类型，返回选择的算子
        for opr in local_oprs:
            if opr['sub_type'] == 0:
                tank_oprs.append(opr)
            elif opr['sub_type'] == 1:
                ifv_oprs.append(opr)
            elif opr['sub_type'] == 2:
                infantry_oprs.append(opr)
            elif opr['sub_type'] == 4:
                uv_oprs.append(opr)
        # 如果是进攻任务
        if attOrdef == TargetTaskType.ToOccupy:
            if tank_oprs:
                return tank_oprs[0]
            if ifv_oprs:
                return ifv_oprs[0]
            if uv_oprs:
                return uv_oprs[0]
            if infantry_oprs:
                return infantry_oprs[0]
        # 如果是防守任务
        else:
            if infantry_oprs:
                return infantry_oprs[0]
            if ifv_oprs:
                return ifv_oprs[0]
            if uv_oprs:
                return uv_oprs[0]
            if tank_oprs:
                return tank_oprs[0]
        return None

    def infer_missle_points(self, observation):
        missile_points = []
        cities_loc = []
        #得到夺控点位置列表
        for city in self.scenario_info['-1']['cities']:
            cities_loc.append(city['coord'])
        #初始位置时
        if observation['time']['cur_step'] == 0:
            #得到敌方算子
            enemies = []
            if self.color == 0:
                for opr in self.scenario_info['1']['operators']:
                    enemies.append(opr)
            else:
                for opr in self.scenario_info['0']['operators']:
                    enemies.append(opr)
            #寻找敌方算子与夺控点之间的连线
            for loc in cities_loc:
                city_hex = HexOffset(loc // 100, loc % 100)
                enemy_opr = random.choice(enemies)
                line = city_hex.get_line(HexOffset(enemy_opr['cur_hex'] // 100, enemy_opr['cur_hex'] % 100))
                #如果直线距离大于5格,取第5格
                if len(line) > 5:
                    missile_points.append(hexoffset_2_mapid(line[4]))
                #如果直线距离小于5格，取最后一格
                else:
                    missile_points.append(hexoffset_2_mapid(line[-1]))
        else:
            #如果观察到算子了,将算子位置信息加入巡航列表
            if self.enemies:
                for enemyInfo in self.enemies:
                    #将最近观察到的算子放在巡逻路线的最前面
                    missile_points.insert(0, enemyInfo['operator']['cur_hex'])
            #如果没有观察到算子，在夺控点周围巡逻
            else:
                pass
        missile_points = missile_points + cities_loc
        return missile_points

    def infer_control_area(self, city, radius):
        """推理射击的夺控点区域"""
        area = []
        city_hex = HexOffset(city['coord'] // 100, city['coord'] % 100)
        # 能否选择半圆，或者根据方向选择扇形
        range_hex = city_hex.get_range(radius)  # 半径的范围

        for hex in range_hex:
            area.append(hex.row * 100 + hex.col)
        return area

    def infer_fire_area(self, point, radius):
        """推理射击的目标区域"""
        area = []
        target_hex = HexOffset(point // 100, point % 100)
        # 能否选择半圆，或者根据方向选择扇形
        range_hex = target_hex.get_range(radius)  # 半径的范围
        for hex in range_hex:
            area.append(hex.row * 100 + hex.col)
        return area

    # 最近一次看到敌人的位置
    def recent_target_area(self):
        area = []
        if self.enemies:
            recent_enemy = self.enemies[0]
            opr = recent_enemy['operator']
            hex = HexOffset(opr['cur_hex'] // 100, opr['cur_hex'] % 100)
            range_hex = hex.get_range(r=3)
            for hex in range_hex:
                area.append(hex.row * 100 + hex.col)
        return area

    # 看到过的敌人距离在5个内的区域
    def recent_near_target(self, opr):
        area = []
        for enemy_info in self.enemies:
            dist_to_recent_enemy = self.map.get_distance(opr['cur_hex'], enemy_info['operator']['cur_hex'])
            # 距离5格以内
            if dist_to_recent_enemy <= 5:
                enemy = enemy_info['operator']
                enemy_hex = HexOffset(enemy['cur_hex'] // 100, enemy['cur_hex'] % 100)
                range_hex = enemy_hex.get_range(r=3)
                for hex in range_hex:
                    area.append(hex.row * 100 + hex.col)
                return area
        return area


    # 最近一次看到敌人所在的方位
    def recent_target_direction(self, cur_hex):
        direction = []
        if self.enemies:
            recent_enemy = self.enemies[0]
            enemy = recent_enemy['operator']
            hex = HexOffset(cur_hex // 100, cur_hex % 100)
            direction.append(hex.get_wedge_direction(HexOffset(enemy['cur_hex'] // 100, enemy['cur_hex'] % 100)))
        return direction

    def adjust_target_area(self, cur_step):
        infantry_area = []
        ifv_area = []
        tank_area = []
        uv_area = []
        # 如果曾经看到过敌人
        # [{obj_id:14977, operator:opr, step:12}]
        if self.enemies:
            for enemyInfo in self.enemies:
                # 如果是坦克
                if enemyInfo['sub_type'] == 0:
                    tank_opr = enemyInfo['operator']
                    tank_hex = HexOffset(tank_opr['cur_hex'] // 100, tank_opr['cur_hex'] % 100)
                    range_hex = tank_hex.get_range(r=10)
                    for hex in range_hex:
                        tank_area.append(hex.row * 100 + hex.col)

                # 如果是战车
                elif enemyInfo['sub_type'] == 1:
                    ifv_opr = enemyInfo['operator']
                    ifv_hex = HexOffset(ifv_opr['cur_hex'] // 100, ifv_opr['cur_hex'] % 100)
                    range_hex = ifv_hex.get_range(r=5)
                    for hex in range_hex:
                        ifv_area.append(hex.row * 100 + hex.col)
                # 如果是人
                elif enemyInfo['sub_type'] == 2:
                    infantry_opr = enemyInfo['operator']
                    infantry_hex = HexOffset(infantry_opr['cur_hex'] // 100, infantry_opr['cur_hex'] % 100)
                    range_hex = infantry_hex.get_range(r=3)
                    for hex in range_hex:
                        infantry_area.append(hex.row * 100 + hex.col)
                # 如果是无人车
                elif enemyInfo['sub_type'] == 4:
                    uv_opr = enemyInfo['operator']
                    uv_hex = HexOffset(uv_opr['cur_hex'] // 100, uv_opr['cur_hex'] % 100)
                    range_hex = uv_hex.get_range(r=5)
                    for hex in range_hex:
                        uv_area.append(hex.row * 100 + hex.col)

            # return [{'infantry': infantry_area, 'ifv': ifv_area, 'tank': tank_area, 'uv': uv_area}]
        return [infantry_area, ifv_area, tank_area, uv_area]


